<!--
/*
 * Matsutake 
 * javascript based template engine 
 * ver 0.1
 * http://code.google.com/p/matsutake/
 *
 * Copyright (c) 2009 Masaaki Takeda (takemasa5)
 * licensed under GPL licenses.
 * 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */


var Matsutake = {

// Constants
	"ANY": new Object(),
	"CHAIN_CHAR": ">+~",
	"TERMINATOR_CHAR": ">+~ ",
	"OPE_EQ": new Object(),
	"OPE_NOT": new Object(),
	"RESERVED_CHARS": "!=<>(){}[]?&%$^|*+-/"
};
	

// Writer Utility
Matsutake.Writer = function(){
	this.buffer = [];
}
Matsutake.Writer.prototype = {
	
	"write": function(data) {
		this.buffer.push(data);
	},
	
	
	"toString": function() {
		return this.buffer.join("");
	}

};



// Config Object
Matsutake.MatsutakeConfig = function(){
	this.rules = [];
	this.filters = [];
};
Matsutake.MatsutakeConfig.prototype = {

	// properties
	
	
	// methods
	"addRule": function(rule, handler) {
		this.rules.push({
			"rule": Matsutake.parseRule(rule),
			"handler": handler
		});
	},
	
	
	"addFilter": function(pattern) {
		this.filters.push(new RegExp(pattern));
	},
	
	
	"is_filter_target": function(name) {
		for(var index = 0 ; index < this.filters.length ; index++) {
			if (name.search(this.filters[index]) != -1) {
				return true;
			}
		}
		return false;
	},


	"attachRule": function(tag_obj) {
	
		var entry;
		for(var index = 0 ; index < this.rules.length ; index++) {
			entry = this.rules[index];
			if (!entry.rule.is_target(tag_obj)){
				continue;
			}
			
			tag_obj.print = entry.handler;
			return;
		}	
	}
};



Matsutake.makeDefaultConfig = function(config) {


	config.addFilter("m:.+");
	
	
	config.addRule("*[m:ignore]", function(writer, context, tag_obj) {
		// do nothing
	});
	config.addRule("*[m:childrenonly]", function(writer, context, tag_obj) {
		Matsutake.outputChildren(writer, context, tag_obj);
	});
	
	
// parse another template 
	config.addRule("*[m:template][m:replace]", function(writer, context, tag_obj){
		var url = tag_obj.getAttribute("m:template");
		Matsutake.parseURL(url);
		
		writer.write(Matsutake.render(url, context));
	});
	config.addRule("*[m:template][m:inner]", function(writer, context, tag_obj){
		var url = tag_obj.getAttribute("m:template");
		Matsutake.parseURL(url);
		
		Matsutake.outputTagBegin(writer, context, tag_obj);
		writer.write(Matsutake.render(url, context));
		Matsutake.outputTagEnd(writer, context, tag_obj);
	});
	

	
	config.addRule("*[m:cond]", function (writer, context, tag_obj){
		var condition = Matsutake.condition_result(context, tag_obj.getAttribute("m:cond"));
		if (condition != undefined && condition != null && condition != false && condition != "false") {
			Matsutake.default_handler(writer, context, tag_obj);
		}
	});
	config.addRule("*[m:replace]", function(writer, context, tag_obj){
		var value = tag_obj.getAttribute("m:replace");
		writer.write(Matsutake.escapeHTML(Matsutake.value_string(context, value)));
	});
	config.addRule("input[type='text'][m:bind]", function(writer, context, tag_obj) {
		tag_obj.attributes["value"] = tag_obj.getAttribute("m:bind");
		Matsutake.default_handler(writer, context, tag_obj);
	});
	config.addRule("textarea[m:bind]", function(writer, context, tag_obj) {
		var value = tag_obj.getAttribute("m:bind");
		
		Matsutake.outputTagBegin(writer, context, tag_obj);
		writer.write(Matsutake.escapeHTML(Matsutake.value_string(context, value)));
		Matsutake.outputTagEnd(writer, context, tag_obj);
	});
	config.addRule("input[type='checkbox'][m:bind]", function(writer, context, tag_obj) {
		var condition = Matsutake.value_string(context, tag_obj.getAttribute("m:bind"));
		var value = Matsutake.value_string(context, tag_obj.getAttribute("value"));
		if (value == condition) {
			tag_obj.attributes["checked"] = null;
		}
		Matsutake.default_handler(writer, context, tag_obj);
	});
	config.addRule("input[type='radio'][m:bind]", function(writer, context, tag_obj) {
		
		var condition = Matsutake.value_string(context, tag_obj.getAttribute("m:bind"));
		var value = Matsutake.value_string(context, tag_obj.getAttribute("value"));
		if (value == condition) {
			tag_obj.attributes["checked"] = null;
		}
		Matsutake.default_handler(writer, context, tag_obj);
	});
	config.addRule("option[m:bind]", function(writer, context, tag_obj) {
		var condition = Matsutake.value_string(context, tag_obj.getAttribute("m:bind"));
		var value = Matsutake.value_string(context, tag_obj.getAttribute("value"));
		if (value == condition) {
			tag_obj.attributes["selected"] = null;
		}
		else{
			delete tag_obj.attributes["selected"];
		}
		Matsutake.default_handler(writer, context, tag_obj);
	});
	config.addRule("*[m:bind]", function(writer, context, tag_obj) {
		var value = tag_obj.getAttribute("m:bind");
		
		Matsutake.outputTagBegin(writer, context, tag_obj);
		writer.write(Matsutake.escapeHTML(Matsutake.value_string(context, value)));
		Matsutake.outputTagEnd(writer, context, tag_obj);
	});
	config.addRule("*[m:bindne]", function(writer, context, tag_obj) {
		var value = tag_obj.getAttribute("m:bindne");
		
		Matsutake.outputTagBegin(writer, context, tag_obj);
		writer.write(Matsutake.value_string(context, value));
		Matsutake.outputTagEnd(writer, context, tag_obj);
	});
	config.addRule("img[m:src]", function(writer, context, tag_obj) {
		tag_obj.attributes["src"] = tag_obj.attributes["m:src"];
		Matsutake.default_handler(writer, context, tag_obj);
	});
	config.addRule("a[m:href]", function(writer, context, tag_obj) {
		tag_obj.attributes["href"] = tag_obj.attributes["m:href"];
		Matsutake.default_handler(writer, context, tag_obj);
	});
	
	
// child loop

	config.addRule("*[m:loopchildren][m:array][m:iterator][m:start][m:end]", function(writer, context, tag_obj) {
		Matsutake.outputTagBegin(writer, context, tag_obj);
		
		var items = Matsutake.value_string(context, tag_obj.getAttribute("m:array"));
		var iterator_name = tag_obj.attributes["m:iterator"];
		var start_index = parseInt(Matsutake.value_string(context, tag_obj.getAttribute("m:start")));
		var end_index = parseInt(Matsutake.value_string(context, tag_obj.getAttribute("m:end")));
		for(var index = start_index ; index < end_index ; index++){
			context[iterator_name] = items[index];
			context[iterator_name + "_index"] = index + 1;
			Matsutake.outputChildren(writer, context, tag_obj);
		}
		
		Matsutake.outputTagEnd(writer, context, tag_obj);
	});
	config.addRule("*[m:loopchildren][m:array][m:iterator][m:start][m:count]", function(writer, context, tag_obj) {
		Matsutake.outputTagBegin(writer, context, tag_obj);
		
		var items = Matsutake.value_string(context, tag_obj.getAttribute("m:array"));
		var iterator_name = tag_obj.attributes["m:iterator"];
		var start_index = parseInt(Matsutake.value_string(context, tag_obj.getAttribute("m:start")));
		var count = parseInt(Matsutake.value_string(context, tag_obj.getAttribute("m:count")));
		for(var index = start_index ; index < items.length && count > 0 ; index++, count--){
			context[iterator_name] = items[index];
			context[iterator_name + "_index"] = index + 1;
			Matsutake.outputChildren(writer, context, tag_obj);
		}
		
		Matsutake.outputTagEnd(writer, context, tag_obj);
	});
	config.addRule("*[m:loopchildren][m:array][m:iterator]", function(writer, context, tag_obj) {
		Matsutake.outputTagBegin(writer, context, tag_obj);
		
		var items = Matsutake.value_string(context, tag_obj.getAttribute("m:array"));
		var iterator_name = tag_obj.attributes["m:iterator"];
		var index = 1;
		for(var key in items){
			context[iterator_name] = items[key];
			context[iterator_name + "_index"] = index;
			Matsutake.outputChildren(writer, context, tag_obj);
			index++;
		}
		
		Matsutake.outputTagEnd(writer, context, tag_obj);
	});
	config.addRule("*[m:loopchildren][m:iterator][m:start][m:end]", function(writer, context, tag_obj) {
		Matsutake.outputTagBegin(writer, context, tag_obj);
		
		var iterator_name = tag_obj.attributes["m:iterator"];
		var start_index = parseInt(Matsutake.value_string(context, tag_obj.getAttribute("m:start")));
		var end_index = parseInt(Matsutake.value_string(context, tag_obj.getAttribute("m:end")));
		for(var index = start_index ; index < end_index ; index++){
			context[iterator_name + "_index"] = index + 1;
			Matsutake.outputChildren(writer, context, tag_obj);
		}
		
		Matsutake.outputTagEnd(writer, context, tag_obj);
	});

	
// loop

	config.addRule("*[m:array][m:iterator][m:start][m:end]", function(writer, context, tag_obj) {
		var items = Matsutake.value_string(context, tag_obj.getAttribute("m:array"));
		var iterator_name = tag_obj.attributes["m:iterator"];
		var start_index = parseInt(Matsutake.value_string(context, tag_obj.getAttribute("m:start")));
		var end_index = parseInt(Matsutake.value_string(context, tag_obj.getAttribute("m:end")));
		for(var index = start_index ; index < end_index ; index++){
			context[iterator_name] = items[index];
			context[iterator_name + "_index"] = index + 1;
			Matsutake.default_handler(writer, context, tag_obj);
		}
	});
	config.addRule("*[m:array][m:iterator][m:start][m:count]", function(writer, context, tag_obj) {
		var items = Matsutake.value_string(context, tag_obj.getAttribute("m:array"));
		var iterator_name = tag_obj.attributes["m:iterator"];
		var start_index = parseInt(Matsutake.value_string(context, tag_obj.getAttribute("m:start")));
		var count = parseInt(Matsutake.value_string(context, tag_obj.getAttribute("m:count")));
		for(var index = start_index ; index < items.length && count > 0 ; index++, count--){
			context[iterator_name] = items[index];
			context[iterator_name + "_index"] = index + 1;
			Matsutake.default_handler(writer, context, tag_obj);
		}
	});
	config.addRule("*[m:array][m:iterator]", function(writer, context, tag_obj) {
		var items = Matsutake.value_string(context, tag_obj.getAttribute("m:array"));
		var iterator_name = tag_obj.attributes["m:iterator"];
		var index = 1;
		for(var key in items){
			context[iterator_name] = items[key];
			context[iterator_name + "_index"] = index;
			Matsutake.default_handler(writer, context, tag_obj);
			index++;
		}
	});
	config.addRule("*[m:iterator][m:start][m:end]", function(writer, context, tag_obj) {
		var iterator_name = tag_obj.attributes["m:iterator"];
		var start_index = parseInt(Matsutake.value_string(context, tag_obj.getAttribute("m:start")));
		var end_index = parseInt(Matsutake.value_string(context, tag_obj.getAttribute("m:end")));
		for(var index = start_index ; index < end_index ; index++){
			context[iterator_name + "_index"] = index + 1;
			Matsutake.default_handler(writer, context, tag_obj);
		}
	});
	
	return config;
}







Matsutake.TextLeaf = function(text) {
	this.objs = Matsutake.makeTextPrintObjs(text);
}
Matsutake.TextLeaf.prototype = {
	
	"print": function(writer, context, tag_obj) {
		
		if (typeof(tag_obj.objs) == "string"){
			writer.write(tag_obj.objs);
			return;
		}
		
		for(var index = 0 ; index < tag_obj.objs.length ; index++) {
			if (typeof(tag_obj.objs[index]) == "object") {
				writer.write(tag_obj.objs[index].print(context));
			}
			else {
				writer.write(tag_obj.objs[index]);
			}
		}
	}
};





Matsutake.TagLeaf = function() {
	this.tag = "";
	this.attributes = {};
	this.children = new Array();
	this.startOnly = false;
	this.parent = null;
}
Matsutake.TagLeaf.prototype = {
	
	"setTag": function(tagname) {
		this.tag = tagname;
		this.startOnly = this.isStartOnly();
	},
	
	
	"appendChild": function(child) {
		child.parent = this;
		this.children.push(child);
	},
	
	
	"addAttribute": function(name, value) {
		this.attributes[name.toLowerCase()] = value;
	},
	
	
	"getAttribute": function(key) {
		
		var result = this.attributes[key + " parsed"];
		if (result == undefined) {
			try{
				result = Matsutake.makeAttrPrintObjs(this.attributes[key]);
				this.attributes[key + " parsed"] = result;
			}catch(e){
				throw "error at " + this.dump() + "\n" + e;
			}
		}
		
		return result;
	},
	
	
	"dump": function(){
		var writer = new Matsutake.Writer();
		this.dump_inner(writer);
		return writer.toString();
	},
	
	
	"dump_inner": function(writer){
		if (this.parent != null) {
			this.parent.dump_inner(writer);
			writer.write(" > ");
		}
		
		writer.write(this.tag);
		if (this.attributes["id"] != undefined){
			writer.write("#" + this.attributes["id"]);
		}
		else if(this.attributes["class"] != undefined){
			writer.write("[" + this.attributes["class"] + "]");
		}
	},
	
	
	"parseVars": function(key) {
		
		var result = this.attributes[key + " parsed"];
		if (result == undefined) {
			result = Matsutake.parse_vars(this.attributes[key]);
			this.attributes[key + " parsed"] = result;
		}
		
		return result;
	},
	
	
	"isStartOnly": function() {
		switch(this.tag.toUpperCase()){
		case "BR":
		case "HR":
		case "META":
		case "BASE":
		case "LINK":
		case "IMG":
		case "INPUT":
		
			return true;
		}
		
		return false;
	},
	
	
	"print": function(writer, context, tag_obj){
		return Matsutake.default_handler(writer, context, tag_obj);
	}
};



Matsutake.default_handler = function(writer, context, tag_obj) {
	
	Matsutake.outputTagBegin(writer, context, tag_obj);	
	Matsutake.outputChildren(writer, context, tag_obj);
	Matsutake.outputTagEnd(writer, context, tag_obj);	
}

Matsutake.parse_vars = function(var_string){
	return [new Matsutake.ContextPart(var_string)];
}

Matsutake.value_array = function(context, value) {
	var buf = [];
	for(item in value) {
		if (typeof(value[item]) == "object") {
			buf.push(value[item].print(context));
		}
		else {
			buf.push(value[item]);
		}
	}
	return buf;
}
Matsutake.value_array_flat = function(context, value) {
	var buf = [];
	var temp;
	for(item in value) {
		if (typeof(value[item]) == "object") {
			temp = value[item].print(context);
		}
		else {
			temp = eval("(" + value[item] + ")");
		}
		
		if (temp.constructor === Array){
			buf = buf.concat(temp);
		}
		else {
			buf.push(temp);
		}
	}
	return buf;
}


Matsutake.value_string = function(context, value) {
	return Matsutake.value_string_quoting(context, value, "");
}
Matsutake.value_string_quoting = function(context, value, quote) {
	
	if (value == null) {
		return null;
	}
	
	if (typeof(value) != "object"){
		return value;
	}

	return value.print(context);	
/*	
	var buf = [];
	for(item in value) {
		if (typeof(value[item]) == "object") {
			buf.push(quote);
			buf.push(value[item].print(context));
			buf.push(quote);
		}
		else {
			buf.push(value[item]);
		}
	}
	return buf.join("");
*/
}


Matsutake.condition_result = function(context, value){

	if (value == null) {
		return null;
	}
	if (typeof(value) != "object"){
		return value;
	}
	
	return value.print(context);
}

Matsutake.cond_string = function(context, value) {
	
	if (value == null) {
		return null;
	}
	
	if (typeof(value) != "object"){
		return value;
	}
	

	var temp;	
	var buf = new Matsutake.Writer();
	for(var item in value) {
		var temp;
		if (typeof(value[item]) == "object") {
			temp = value[item].print(context);
			if (temp == ""){
				buf.write("undefined");
			}
			else if(!isNaN(temp)){
				buf.write(temp);
			}
			else {
				buf.write("'");
				buf.write(temp);
				buf.write("'");
			}
		}
		else {
			buf.write(value[item]);
		}
		
	}
	return buf.toString();
}

Matsutake.replaceAll = function(str, pattern, dest){
	var pos = 0;
	while(-1 != (pos = str.indexOf(pattern, pos))){
		str = str.substring(0, pos) + dest + str.substring(pos + pattern.length);
		pos += dest.length;
	}
	return str;
}
Matsutake.escapeHTML = function(str) {
	return Matsutake.replaceAll(Matsutake.replaceAll(Matsutake.replaceAll(String(str), "<", "&lt;"), ">", "&gt;"), "&", "&amp;");
}
Matsutake.unescapeHTML = function(str) {
	return Matsutake.replaceAll(Matsutake.replaceAll(Matsutake.replaceAll(String(str), "&lt;", "<"), "&gt;", ">"), "&amp;", "&");
//	return str.replace("&lt;", "<").replace("&gt;", ">").replace("&amp;", "&");
}

Matsutake.outputTagBegin = function(writer, context, tag_obj) {
	if (tag_obj.tag != "") {
		writer.write("<");
		writer.write(tag_obj.tag);
		
		for(var attr in tag_obj.attributes) {
			if (Matsutake.config.is_filter_target(attr)) {
				continue;
			}
			if (attr.indexOf(" ") != -1) {
				continue;
			}
			
			
			var value = Matsutake.value_string(context, tag_obj.getAttribute(attr));
			
			if (value == null) {
				writer.write(" ");
				writer.write(attr);
			}
			else {
				writer.write(" ");
				writer.write(attr);
				writer.write('="');
				writer.write(value);
				writer.write('"');
			}
		}
		writer.write(">");
	}
}
Matsutake.outputTagEnd = function(writer, context, tag_obj) {
	if (tag_obj.tag != "" && !tag_obj.startOnly) {
		writer.write("</");
		writer.write(tag_obj.tag);
		writer.write(">");
	}
}
Matsutake.outputChildren = function(writer, context, tag_obj) {
	
	for(var index = 0 ; index < tag_obj.children.length ; index++) {
		tag_obj.children[index].print(writer, context, tag_obj.children[index]);
	}
}


Matsutake.loadAndParse_html = function(src) {
	var root_node = new Matsutake.TagLeaf();
	Matsutake.parse_html(src, 0, root_node);
	
	return root_node;
}

	
Matsutake.parse_html = function(src, base_pos, parent) {
	
	if (src == null){
		throw "src == null @";
	}
	
	var start_pos = base_pos;
	var pos = start_pos;
	var comment_block = false;
	var length = src.length;
	var ch;
	var text_buf = "";
	for(; pos < length ;){
		if (comment_block) {
			if ((pos + 3) < length && 
				src.charAt(pos + 0) == '-' && 
				src.charAt(pos + 1) == '-' && 
				src.charAt(pos + 2) == '>'
			) {
				comment_block = false;
				text_buf += "-" + "->";
				pos += 3;
			} 
			else {
				text_buf += src.charAt(pos);
				pos++;
			}
			continue;
		}
		
		ch = src.charAt(pos);
		if (ch != '<') {
			text_buf += ch;
			pos++;
			continue;
		}
		if ((pos + 4) < length &&
			src.charAt(pos + 1) == '!' &&
			src.charAt(pos + 2) == '-' &&
			src.charAt(pos + 3) == '-'
		) {
			comment_block = true;
			text_buf += "<" + "!--";
			pos += 4;
			continue;
		}
		
		
		// テキストノードがあれば追加
		if (text_buf != "") {
			parent.appendChild(new Matsutake.TextLeaf(text_buf));
			text_buf = "";
		}
		
		
		if ((pos + 1) < length && 
			src.charAt(pos + 1) == '/' &&
			src.substr(pos + 2, parent.tag.length) == parent.tag
		) {
			pos = Matsutake.skip_close_tag(src, pos);
			break;
		}
		
		
		// タグ開始位置
		var current = new Matsutake.TagLeaf();
		var parsed_pos = Matsutake.parse_tag(src, pos + 1, current);
		if (parsed_pos == -1) {
			text_buf += ch;
			pos++;
			continue;
		}
		pos = parsed_pos;


		// 親オブジェクトに追加
		parent.appendChild(current);
		
		// Rule適用
		Matsutake.config.attachRule(current);


		
		if (!current.startOnly) {
			// 再帰呼び出し
			try{
				pos = Matsutake.parse_html(src, pos, current);
			}
			catch(e){
				throw e + " parent.tag";
			}
		}
		
	}
	
	// テキストノードがあれば追加
	if (text_buf != "") {
		parent.appendChild(new Matsutake.TextLeaf(text_buf));
		text_buf = "";
	}
	return pos;
}

Matsutake.skip_close_tag = function(src, pos) {
	var length = src.length;
	
	while(pos < length) {
		if (src.charAt(pos++) == ">") {
			break;
		}
	}
	
	return pos;
}

// タグとして使える文字かどうか
Matsutake.is_tag_char = function(ch) {
	if (ch >= 'a' && ch <= 'z') {
		return true;
	}
	if (ch >= 'A' && ch <= 'Z') {
		return true;
	}
	if (ch >= '0' && ch <= '9') {
		return true;
	}
	
	return ":_ >".indexOf(ch) == -1 ? false : true;
}

Matsutake.parse_tag = function(src, base_pos, tag) {
	
	var pos = base_pos;
	var start_pos = base_pos;
	var length = src.length;
	var ch;
	
	// タグ名
	// 半角スペースか＞がくるまで
	while(pos < length) {
		ch = src.charAt(pos);
		if (!Matsutake.is_tag_char(ch)) {
			break;
		}
		if (ch == ' ' || ch == '>') {
			tag.setTag(src.substring(start_pos, pos));
			break;
		}
		pos++;
	}
	
	if (tag.tag == ""){
		return -1;
	}
	
	
	/*
	 * attribute
	 * name="value" 又は name という構造
	 * ＞がくるまでループ
	 */
	var key = "";
	var value = "";
	var esc_ch = "";
	var is_value = 0;
	start_pos = pos;
	while(pos < length) {
		ch = src.charAt(pos++);
		if (is_value == 0 && ch == '=') {
			key = src.substring(start_pos, pos - 1);
			is_value = 1;
			start_pos = pos;
			continue;
		}
		if (esc_ch != "") {
			if (ch == esc_ch) {
				esc_ch = "";
			}
			continue;
		}
		if (is_value == 1) {
			is_value = 2;
			switch(ch) {
			case "'":
			case '"':
				esc_ch = ch;
				continue;
			}
		}

		
		if (ch == '>' || ch == ' ') {
			if (!is_value) {
				// keyしかないパターン
				// selectedやcheckedなど
				key = src.substring(start_pos, pos - 1);
				value = "";
			}
			else {
				value = src.substring(start_pos, pos - 1);
			}
			
			if (key != "") {
				tag.addAttribute(key, Matsutake.unquote(value));
			}
			
			key = "";
			value = "";
			is_value = 0;
			start_pos = pos;
		}
		
		
		if (ch == '>') {
			break;
		}
	}
	
	return pos;
}

Matsutake.unquote = function(src) {
	
	if (src == null) {
		return null;
	}
	
	var ch = src.charAt(0);
	switch(ch) {
	case "'":
	case '"':
		if (src.charAt(src.length - 1) == ch) {
			// 先頭と末尾を削除
			return src.substring(1, src.length - 1);
		}
		break;
	}
	
	return src;
}



Matsutake.ContextPart = function(key) {
	var pos = key.indexOf(".");
	if (pos == -1){
		this.key = key;
	}
	else{
		this.key = key.substring(0, pos);
		this.subkeys = [];
		
		var lastpos = pos;
		while(-1 != (pos = key.indexOf(".", pos + 1))){
			this.subkeys.push(key.substring(lastpos + 1, pos));
			lastpos = pos;
		}
		this.subkeys.push(key.substring(lastpos + 1));
	}
}
Matsutake.ContextPart.prototype.print = function(context) {

	var v = context[this.key];
	if (v == undefined){
		return "";
	}
	
	var count = this.subkeys == undefined ? 0 : this.subkeys.length;
	for(var index = 0 ; index < count ; index++){
		v = v[this.subkeys[index]];
	}
	
	return v;
}


Matsutake.EvalPart = function(key){

//	this.generated = eval("(function(ctx){ return " + Matsutake.unescapeHTML(this.generate(key)) + "})");
	eval("var func = function(ctx){ return " + Matsutake.unescapeHTML(this.generate(key)) + "}");
	this.generated = func;
};

Matsutake.EvalPart.prototype = {
	
	"generated": null,
	
	
	"generate": function(key){
		
		var spos = key.search(/\$\{[.A-Za-z0-9_-]+\}/);
		if (spos == -1){
			return key;
		}
		
		var result = key.match(/\$\{([.A-Za-z0-9_-]+)\}/);
		var matched = result[0];
		var argname = result[1];
		
		var arg_tail = "";
		var tpos = argname.indexOf("."); 
		if (-1 != tpos){
			arg_tail = argname.substring(tpos) + " ";
			argname = argname.substring(0, tpos);
		}
		
		var gen = key.substring(0, spos) + 'ctx["' + argname + '"]' + arg_tail + key.substring(spos + matched.length);
		return this.generate(gen);
	},
	
	
	
	"print": function(context) {
	
		return this.generated(context);
	}
}
	

Matsutake.makeAttrPrintObjs = function(src) { 
	
	if (src == undefined){
		return null;
	}
	if (src == null) {
		return null;
	}
	if (typeof(src) != "string"){
		throw "src != String";
	}
	if (-1 == src.search(/\$\{[.A-Za-z0-9_-]+\}/)){
		return src;
	}
	
	return new Matsutake.EvalPart(src);
}


Matsutake.makeTextPrintObjs = function(src) { 
	
	if (src == undefined){
		return null;
	}
	if (src == null) {
		return null;
	}
	if (typeof(src) != "string"){
		throw "src != String";
	}
	
	
	// ${key} を変換する
	var objs = new Array();
	
	
//	if (-1 == src.search(/\${[a-z0-9-_.]+}/i)){
	if (-1 == src.search(/\${[a-z0-9-_.() ,']+}/i)){
		return src;
	}
	
	
	var ch;
	var pos = 0;
	var start = 0;
	var temp;
	while(pos < src.length) {
		
		ch = src.charAt(pos);
		
		if (ch == '$'){
			if (src.charAt(pos + 1) == '{') {
				temp = Matsutake.findTokenEnd(src, pos + 2);
				if (temp == -1) {
					// NG
					pos++;
					continue;
				}
				
				// OK
				// 切り出す
				if (start != pos) {
					objs.push(src.substring(start, pos));
				}
				objs.push(new Matsutake.ContextPart(src.substring(pos + 2, temp)));
				pos = temp + 1;
				start = pos;
			}
			else{
				pos += 2;
			}
		}
		else {
			pos++;
		}
	}
	
	if (start != pos) {
		objs.push(src.substring(start));
	}
	
	return objs;
}



Matsutake.findTokenEnd = function(src, pos) {
	var p = pos;
	var ch;
	while(p < src.length) {
		ch = src.charAt(p);
		if (ch == '}'){
			return p;
		}
/*
		if (ch == '.') {
			// ok
		}
		else if(is_tag_char(ch)){
			// ok
		}
		else{
			return -1;
		}
*/		
		p++;
	}
	return -1;
}




Matsutake.SingleRule = function(added){
	this.rule = added;
}
Matsutake.SingleRule.prototype.is_target = function(tag_obj) {
	
	if (this.rule.tagname != "*" && this.rule.tagname != tag_obj.tag.toLowerCase()) {
		return false;
	}

	var attr;
	var value;
	for(var index = 0 ; index < this.rule.attr.length ; index++) {
		
		attr = this.rule.attr[index];
		value = tag_obj.attributes[attr.key.toLowerCase()];
		if (value == undefined) {
			return false;
		}
		
		switch(attr.operator){
		case Matsutake.OPE_EQ:
			if (attr.value != Matsutake.ANY && value != attr.value) {
				return false;
			}
			break;
		
		case Matsutake.OPE_NOT:
			if (value == attr.value) {
				return false;
			}
			break;
		
		}
	}	
	
	return true;
}



Matsutake.AncestorRuleChain = function(ancestor, descendant){
	this.ancestor = ancestor;
	this.descendant = descendant;
}
Matsutake.AncestorRuleChain.prototype.is_target = function(tag_obj){
	if (this.descendant.is_target(tag_obj)){
		var parent = tag_obj.parent;
		while(parent != null){
			if (this.ancestor.is_target(parent)){
				return true;
			}
			
			parent = parent.parent;
		}
	}
	return false;
}


Matsutake.ParentRuleChain = function(parent, child){
	this.parent = parent;
	this.child = child;
}
Matsutake.ParentRuleChain.prototype.is_target = function(tag_obj){
	if (this.child.is_target(tag_obj)){
		if (tag_obj.parent != null && this.parent.is_target(tag_obj.parent)){
			return true;
		}
	}
	return false;
}


Matsutake.PrevNextRuleChain = function(prev_rule, next_rule){
	this.prev_rule = prev_rule;
	this.next_rule = next_rule;
}
Matsutake.PrevNextRuleChain.prototype.is_target = function(tag_obj){
	if (tag_obj.parent != null && this.next_rule.is_target(tag_obj)){
		var parent = tag_obj.parent;
		var lastnode = null;
		
		for(var index = 0 ; index < parent.children.length ; index++){
			if (tag_obj == parent.children[index]){
				return lastnode != null;
			}
			lastnode = this.prev_rule.is_target(parent.children[index]) ? parent.children[index] : null;
		}
	}
	return false;
}


Matsutake.PrevSiblingsRuleChain = function(prev_rule, sib_rule){
	this.prev_rule = prev_rule;
	this.sib_rule = sib_rule;
}
Matsutake.PrevSiblingsRuleChain.prototype.is_target = function(tag_obj){
	if (tag_obj.parent != null && this.sib_rule.is_target(tag_obj)){
		var parent = tag_obj.parent;
		for(var index = 0 ; index < parent.children.length ; index++){
			if (tag_obj == parent.children[index]){
				return false;
			}
			if (this.prev_rule.is_target(parent.children[index])){
				return true;
			}
		}
	}
	return false;
}


Matsutake.parseRule = function(pattern){

	var pos = 0;
	var parsed = null;
	var result = null;
	var chain = "";
	
	
	parsed = Matsutake.parseSelector(pattern, pos);
	result = parsed.rule;
	pos = parsed.endpos;
	
	for(; pos < pattern.length ;){
		
		if (pattern.charAt(pos) == " "){
			pos++;
			continue;
		}
			
		switch(pattern.charAt(pos)){
		case ">":
			parsed = Matsutake.parseSelector(pattern, pos + 1);
			result = new Matsutake.ParentRuleChain(result, parsed.rule);
			break;
			
		case "+":
			parsed = Matsutake.parseSelector(pattern, pos + 1);
			result = new Matsutake.PrevNextRuleChain(result, parsed.rule);
			break;
			
		case "~":
			parsed = Matsutake.parseSelector(pattern, pos + 1);
			result = new Matsutake.PrevSiblingsRuleChain(result, parsed.rule);
			break;
			
		default:
			parsed = Matsutake.parseSelector(pattern, pos);
			result = new Matsutake.AncestorRuleChain(result, parsed.rule);
			break;
			
		}

		
		pos = parsed.endpos;
		
	}
	
	
	return result;
}

Matsutake.parseSelector = function(pattern, pos) {
	

	var ch;
	var start = pos;
	var tagname = null;
	var attr_filters = new Array();
	
	while(pos < pattern.length && pattern.charAt(pos) == " ") {
		start++;
		pos++;
	}
	
	while(pos < pattern.length) {
		
		ch = pattern.charAt(pos);
		if (ch == '['){
			if (start != pos){
				tagname = pattern.substring(start, pos);
			}
			if (tagname == ""){
				throw "tagname must be set. src=[" + pattern + "]";
			}
			
			pos = Matsutake.parseAttrFilter(pattern, pos + 1, attr_filters);
			start = pos;
		}
		else if(-1 != Matsutake.TERMINATOR_CHAR.indexOf(ch)){
			break;
		}
		else {
			pos++;
		}
	}
	if (tagname == null && start != pos){
		tagname = pattern.substring(start, pos).toLowerCase();
	}
	
	return {
		"endpos": pos,
		"rule": new Matsutake.SingleRule({
					"tagname": tagname == null ? pattern : tagname,
					"attr": attr_filters
					})
	};
}



Matsutake.parseAttrFilter = function(pattern, parse_start, filters) {
	
	var ch;
	var start = parse_start;
	var pos = start;
	var key = null;
	var value = null;
	var ope = null;
	var quote = null;
	var mode = "key";
	var current = "";
	var delim = false;
	
	for(;;){
		ch = pattern.charAt(pos);
		
		if (quote != null){
			if (quote == ch) {
				quote = null;
			}
			else {
				current += ch;
			}
			pos++;
			continue;
		}
		
		
		
		delim = false;
		if (mode == "ope" && -1 == Matsutake.RESERVED_CHARS.indexOf(ch)){
			delim = true;
		}
		
		
		if (delim || -1 != Matsutake.RESERVED_CHARS.indexOf(ch)) {
			
			switch(mode) {
			case "key":
				key = current;
				mode = "ope";
				break;
			
			case "ope":
				switch(current) {
				case "=":
					ope = Matsutake.OPE_EQ;
					break;
				
				case "!=":
					ope = Matsutake.OPE_NOT;
					break;
					
				default:
					throw "unknown operator. src=[" + pattern + "]";
				}
				mode = "value";
				break;
				
			case "value":
				value = current;
			}
			current = "";
		}
		
		
				
		if (ch == '"' || ch == "'"){
			quote = ch;
			pos++;
			continue;
		}
		if (ch == ']') {
			
			if (key == null || key == ""){
				throw "attribute name must be set. src=[" + pattern + "]";
			}
			
			filters.push({
				"key": key,
				"value": value == null ? Matsutake.ANY : value,
				"operator": ope == null ? Matsutake.OPE_EQ : ope
			});
			
			return pos + 1;
		}
		
		
		
		if (ch != ' ') {
			current += ch;
		}
		pos++;
	}
}



	
// properties
Matsutake.config = Matsutake.makeDefaultConfig(new Matsutake.MatsutakeConfig());
Matsutake.parsed_map = {};
	

// methods	
Matsutake.init = function(config) {
	this.config = config;
};


Matsutake.remove = function(key) {
	delete this.parsed_map.key;
};


Matsutake.parseString = function(key, src){
	var root_node = new Matsutake.TagLeaf();
	Matsutake.parse_html(src, 0, root_node);

	this.parsed_map[key] = root_node;	
};


Matsutake.parseURL = function(url){
	var entry = this.parsed_map[url];
	jQuery.ajax({
		"async": false, 
		"cache": false, 
		"type": "get",
		"url": url, 
		"ifModified": entry == null ? false : true,
		"success": function(body){
			Matsutake.parseString(url, body);
		},
		"error": null}
	);
};


Matsutake.render = function(key, context){
	var writer = new Matsutake.Writer();
	var root = this.parsed_map[key];
	root.print(writer, context, root);
	
	return writer.toString();
};


-->